123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 |
- <template>
- <div class="flex h-screen">
- <div
- class="w-80 bg-white py-6 relative border-l border-gray-100 flex flex-col"
- >
- <workflow-edit-block
- v-if="state.isEditBlock"
- :data="state.blockData"
- @update="updateBlockData"
- @close="(state.isEditBlock = false), (state.blockData = {})"
- />
- <workflow-details-card
- v-else
- :workflow="workflow"
- :data-changed="state.isDataChanged"
- @save="saveWorkflow"
- @export="exportWorkflow"
- @execute="executeWorkflow"
- @update="updateWorkflow"
- @showDataColumns="state.showDataColumnsModal = true"
- @showSettings="state.showSettings = true"
- @rename="renameWorkflow"
- @delete="deleteWorkflow"
- />
- </div>
- <div class="flex-1 relative overflow-auto">
- <div class="absolute px-3 rounded-lg bg-white z-10 left-0 m-4 top-0">
- <ui-tabs v-model="activeTab" class="border-none h-full space-x-1">
- <ui-tab value="editor">Editor</ui-tab>
- <ui-tab value="logs">Logs</ui-tab>
- <ui-tab value="running" class="flex items-center">
- Running
- <span
- v-if="workflowState.length > 0"
- class="
- ml-2
- p-1
- text-center
- inline-block
- text-xs
- rounded-full
- bg-black
- text-white
- "
- style="min-width: 25px"
- >
- {{ workflowState.length }}
- </span>
- </ui-tab>
- </ui-tabs>
- </div>
- <keep-alive>
- <workflow-builder
- v-if="activeTab === 'editor'"
- class="h-full w-full"
- :data="workflow.drawflow"
- @load="editor = $event"
- @deleteBlock="deleteBlock"
- />
- <div v-else class="container pb-4 mt-24 px-4">
- <template v-if="activeTab === 'logs'">
- <div v-if="logs.length === 0" class="text-center">
- <img
- src="@/assets/svg/files-and-folder.svg"
- class="mx-auto max-w-sm"
- />
- <p class="text-xl font-semibold">No data to show</p>
- </div>
- <shared-logs-table :logs="logs" class="w-full">
- <template #item-append="{ log: itemLog }">
- <td class="text-right">
- <v-remixicon
- name="riDeleteBin7Line"
- class="inline-block text-red-500 cursor-pointer"
- @click="deleteLog(itemLog.id)"
- />
- </td>
- </template>
- </shared-logs-table>
- </template>
- <template v-else-if="activeTab === 'running'">
- <div v-if="workflowState.length === 0" class="text-center">
- <img
- src="@/assets/svg/files-and-folder.svg"
- class="mx-auto max-w-sm"
- />
- <p class="text-xl font-semibold">No data to show</p>
- </div>
- <div class="grid grid-cols-2 gap-4">
- <shared-workflow-state
- v-for="item in workflowState"
- :id="item.id"
- :key="item.id"
- :state="item.state"
- />
- </div>
- </template>
- </div>
- </keep-alive>
- </div>
- </div>
- <ui-modal v-model="state.showDataColumnsModal" content-class="max-w-xl">
- <template #header>Data columns</template>
- <workflow-data-columns
- v-bind="{ workflow }"
- @update="updateWorkflow"
- @close="state.showDataColumnsModal = false"
- />
- </ui-modal>
- <ui-modal v-model="state.showSettings">
- <template #header>Workflow settings</template>
- <workflow-settings v-bind="{ workflow }" @update="updateWorkflow" />
- </ui-modal>
- </template>
- <script setup>
- /* eslint-disable consistent-return */
- import {
- computed,
- reactive,
- shallowRef,
- provide,
- onMounted,
- onUnmounted,
- } from 'vue';
- import { useStore } from 'vuex';
- import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router';
- import browser from 'webextension-polyfill';
- import emitter from 'tiny-emitter/instance';
- import { sendMessage } from '@/utils/message';
- import { debounce } from '@/utils/helper';
- import { useDialog } from '@/composable/dialog';
- import { exportWorkflow } from '@/utils/workflow-data';
- import Log from '@/models/log';
- import Workflow from '@/models/workflow';
- import WorkflowBuilder from '@/components/newtab/workflow/WorkflowBuilder.vue';
- import WorkflowSettings from '@/components/newtab/workflow/WorkflowSettings.vue';
- import WorkflowEditBlock from '@/components/newtab/workflow/WorkflowEditBlock.vue';
- import WorkflowDetailsCard from '@/components/newtab/workflow/WorkflowDetailsCard.vue';
- import WorkflowDataColumns from '@/components/newtab/workflow/WorkflowDataColumns.vue';
- import SharedLogsTable from '@/components/newtab/shared/SharedLogsTable.vue';
- import SharedWorkflowState from '@/components/newtab/shared/SharedWorkflowState.vue';
- const store = useStore();
- const route = useRoute();
- const router = useRouter();
- const dialog = useDialog();
- const workflowId = route.params.id;
- const editor = shallowRef(null);
- const activeTab = shallowRef('editor');
- const state = reactive({
- blockData: {},
- isEditBlock: false,
- showSettings: false,
- isDataChanged: false,
- showDataColumnsModal: false,
- });
- const workflowState = computed(() =>
- store.getters.getWorkflowState(workflowId)
- );
- const workflow = computed(() => Workflow.find(workflowId) || {});
- const logs = computed(() =>
- Log.query().where('workflowId', workflowId).orderBy('startedAt', 'desc').get()
- );
- const updateBlockData = debounce((data) => {
- state.blockData.data = data;
- state.isDataChanged = true;
- editor.value.updateNodeDataFromId(state.blockData.blockId, data);
- const inputEl = document.querySelector(
- `#node-${state.blockData.blockId} input.trigger`
- );
- if (inputEl) inputEl.dispatchEvent(new Event('change'));
- }, 250);
- function deleteLog(logId) {
- Log.delete(logId).then(() => {
- store.dispatch('saveToStorage', 'logs');
- });
- }
- function deleteBlock(id) {
- if (state.isEditBlock && state.blockData.blockId === id) {
- state.isEditBlock = false;
- state.blockData = {};
- }
- state.isDataChanged = true;
- }
- function updateWorkflow(data) {
- return Workflow.update({
- where: workflowId,
- data,
- });
- }
- async function handleWorkflowTrigger({ data }) {
- try {
- const workflowAlarm = await browser.alarms.get(workflowId);
- const { visitWebTriggers, shortcuts } = await browser.storage.local.get([
- 'visitWebTriggers',
- 'shortcuts',
- ]);
- let visitWebTriggerIndex = visitWebTriggers.findIndex(
- (item) => item.id === workflowId
- );
- const keyboardShortcuts = Array.isArray(shortcuts) ? {} : shortcuts || {};
- delete keyboardShortcuts[workflowId];
- if (workflowAlarm) await browser.alarms.clear(workflowId);
- if (visitWebTriggerIndex !== -1) {
- visitWebTriggers.splice(visitWebTriggerIndex, 1);
- visitWebTriggerIndex = -1;
- }
- await browser.storage.local.set({
- visitWebTriggers,
- shortcuts: keyboardShortcuts,
- });
- if (['date', 'interval'].includes(data.type)) {
- let alarmInfo;
- if (data.type === 'date') {
- alarmInfo = {
- when: data.date ? new Date(data.date).getTime() : Date.now() + 60000,
- };
- } else {
- alarmInfo = {
- periodInMinutes: data.interval,
- };
- if (data.delay > 0) alarmInfo.delayInMinutes = data.delay;
- }
- if (alarmInfo) await browser.alarms.create(workflowId, alarmInfo);
- } else if (data.type === 'visit-web' && data.url.trim() !== '') {
- const payload = {
- id: workflowId,
- url: data.url,
- isRegex: data.isUrlRegex,
- };
- if (visitWebTriggerIndex === -1) {
- visitWebTriggers.unshift(payload);
- } else {
- visitWebTriggers[visitWebTriggerIndex] = payload;
- }
- await browser.storage.local.set({ visitWebTriggers });
- } else if (data.type === 'keyboard-shortcut') {
- keyboardShortcuts[workflowId] = data.shortcut;
- await browser.storage.local.set({ shortcuts: keyboardShortcuts });
- }
- } catch (error) {
- console.error(error);
- }
- }
- function saveWorkflow() {
- const data = editor.value.export();
- updateWorkflow({ drawflow: JSON.stringify(data) }).then(() => {
- const [triggerBlockId] = editor.value.getNodesFromName('trigger');
- if (triggerBlockId) {
- handleWorkflowTrigger(editor.value.getNodeFromId(triggerBlockId));
- }
- state.isDataChanged = false;
- });
- }
- function editBlock(data) {
- state.isEditBlock = true;
- state.blockData = data;
- }
- function executeWorkflow() {
- if (editor.value.getNodesFromName('trigger').length === 0) {
- /* eslint-disable-next-line */
- alert("Can't find a trigger block");
- return;
- }
- const payload = {
- ...workflow.value,
- drawflow: editor.value.export(),
- isTesting: state.isDataChanged,
- };
- sendMessage('workflow:execute', payload, 'background');
- }
- function handleEditorDataChanged() {
- state.isDataChanged = true;
- }
- function deleteWorkflow() {
- dialog.confirm({
- title: 'Delete workflow',
- okVariant: 'danger',
- body: `Are you sure you want to delete "${workflow.value.name}" workflow?`,
- onConfirm: () => {
- Workflow.delete(route.params.id).then(() => {
- router.replace('/workflows');
- });
- },
- });
- }
- function renameWorkflow() {
- dialog.prompt({
- title: 'Rename workflow',
- placeholder: 'Workflow name',
- okText: 'Rename',
- inputValue: workflow.value.name,
- onConfirm: (newName) => {
- Workflow.update({
- where: route.params.id,
- data: {
- name: newName,
- },
- });
- },
- });
- }
- provide('workflow', {
- data: workflow,
- updateWorkflow,
- /* eslint-disable-next-line */
- showDataColumnsModal: (show = true) => (state.showDataColumnsModal = show),
- });
- onBeforeRouteLeave(() => {
- if (!state.isDataChanged) return;
- const answer = window.confirm(
- 'Do you really want to leave? you have unsaved changes!'
- );
- if (!answer) return false;
- });
- onMounted(() => {
- const isWorkflowExists = Workflow.query().where('id', workflowId).exists();
- if (!isWorkflowExists) {
- router.push('/workflows');
- }
- window.onbeforeunload = () => {
- if (state.isDataChanged) {
- return 'Changes you made may not be saved.';
- }
- };
- emitter.on('editor:edit-block', editBlock);
- emitter.on('editor:data-changed', handleEditorDataChanged);
- });
- onUnmounted(() => {
- window.onbeforeunload = null;
- emitter.off('editor:edit-block', editBlock);
- emitter.off('editor:data-changed', handleEditorDataChanged);
- });
- </script>
- <style>
- .ghost-task {
- height: 40px;
- @apply bg-box-transparent;
- }
- .ghost-task:not(.workflow-task) * {
- display: none;
- }
- </style>
|